#!/usr/bin/ruby

require "opengl"
require "glut"

class Frame
	attr_accessor :fitness, :strategy, :coop, :tft
	def self.single_from_file(file=$stdin)
		f = Frame.new
		f.strategy = file.gets.split(/\s+/).map!{|s| s.to_f}
		f.fitness = file.gets.split(/\s+/).map!{|s| s.to_f}
		s = 0.0
		t = 0.0
		f.strategy.each do |st| 
			s += [st,0].max 
			t += 1.0 if st == -2.0
		end
		f.coop = s/f.strategy.size
		f.tft  = t/f.strategy.size
		$max_fitness = [f.fitness.max,$max_fitness].max
		file.gets
		f
	end
	def self.skip(file=$stdin)
		file.gets
		file.gets
		file.gets
	end
	def self.multiple_from_file(file=$stdin)
		result = []
		until file.eof? do
			result << self.single_from_file(file)
			print "\r#{result.size}"
			$nth.times do self.skip(file) end
		end
		print "\rwczytano #{result.size} epok\n"
		result
	end
end

$width = 0
$height = 0
$window_width = 500
$window_height = 500
$nth = [(ARGV[1] || 1).to_i,1].max - 1

$time = 0
$mean = false
$animate = false
$fullscreen = false
$vsplit = false
$grid = true
$data = []
$fitdata = []
$coopdata = []
$stgdata = []
$max_fitness = 0

def parse(file)
	$width, $height, $type = file.gets.split(/\s+/)
	$width, $height = $width.to_i, $height.to_i
	#unless $type =~ /graf4|graf6|graf8/ then
	#	print "graph #{$type} is not supported\n"
	#	exit 1
	#end
	file.gets
	$data = Frame.multiple_from_file(file)
end

def color(float)
	if float < 0.3 then
		[0.0, float/0.3, 1.0-float/0.3]
	elsif float < 0.6 then
		float -= 0.3
		[float/0.3, 1.0, 0.0]
	else
		float -= 0.6
		[1.0, 1.0 - float/0.4, 0.0]
	end
end

def update
	$height.times do |y|
		$width.times do |x|
			fitness = 0
			if $mean then
				t = $data[([$time-10,0].max..[$time+10,$data.size-1].min)]
				t.each do |d| fitness += d.fitness[x+y*$width] end
				fitness /= t.size*$max_fitness
			else
				fitness = $data[$time].fitness[x+y*$width]/$max_fitness
			end
			colors = color(fitness)
			$fitdata[x*3+y*$width*3+0] = colors[0]
			$fitdata[x*3+y*$width*3+1] = colors[1]
			$fitdata[x*3+y*$width*3+2] = colors[2]
			strategy = 1.0 - $data[$time].strategy[x+y*$width]
			if strategy <= 1 then
				$stgdata[x*3+y*$width*3+0] = strategy
				$stgdata[x*3+y*$width*3+1] = strategy
				$stgdata[x*3+y*$width*3+2] = strategy
			elsif strategy == 3 then
				$stgdata[x*3+y*$width*3+0] = 1.0
				$stgdata[x*3+y*$width*3+1] = 0.0
				$stgdata[x*3+y*$width*3+2] = 0.0
			elsif strategy == 2 then
				$stgdata[x*3+y*$width*3+0] = 0.5
				$stgdata[x*3+y*$width*3+1] = 0.5
				$stgdata[x*3+y*$width*3+2] = 0.5
			else
				$stgdata[x*3+y*$width*3+0] = 1.0
				$stgdata[x*3+y*$width*3+1] = 0.0
				$stgdata[x*3+y*$width*3+2] = 1.0
			end
		end
	end
	$rawfitdata = $fitdata.pack("f*")
	$rawstgdata = $stgdata.pack("f*")
end

def coopup
	$data.size.times do |x|
		100.times do |y|
			if y < $data[x].coop*100 then
				$coopdata[x*3+y*$data.size*3+0] = 0.0
				$coopdata[x*3+y*$data.size*3+1] = 0.0
				$coopdata[x*3+y*$data.size*3+2] = 0.0
			elsif y < ($data[x].coop+$data[x].tft)*100 then
				$coopdata[x*3+y*$data.size*3+0] = 1.0
				$coopdata[x*3+y*$data.size*3+1] = 0.0
				$coopdata[x*3+y*$data.size*3+2] = 0.0
			else
				$coopdata[x*3+y*$data.size*3+0] = 1.0
				$coopdata[x*3+y*$data.size*3+1] = 1.0
				$coopdata[x*3+y*$data.size*3+2] = 1.0
			end
		end
	end
	$rawcoopdata = $coopdata.pack("f*")
end

$display = Proc.new do
	GL.Clear(GL::COLOR_BUFFER_BIT);
	GL.RasterPos2i(0, 0);
	GL.PixelZoom($window_width.to_f/$data.size,$window_height.to_f/3.0/100.0);
	GL.DrawPixels($data.size, 100, GL::RGB, GL::FLOAT, $rawcoopdata);
	if $vsplit then
		GL.PixelZoom($window_width.to_f/$width.to_f/2.0,$window_height.to_f/$height.to_f*2.0/3.0);
		GL.RasterPos2i(0, $window_height.to_f/3.0+0.5);
		GL.DrawPixels($width, $height, GL::RGB, GL::FLOAT, $rawfitdata);
		GL.RasterPos2i($window_width.to_f/2.0+0.5, $window_height.to_f/3.0+0.5);
		GL.DrawPixels($width, $height, GL::RGB, GL::FLOAT, $rawstgdata);
	else
		GL.PixelZoom($window_width.to_f/$width.to_f,$window_height.to_f/$height.to_f/3.0);
		GL.RasterPos2i(0, $window_height.to_f/3.0+0.5);
		GL.DrawPixels($width, $height, GL::RGB, GL::FLOAT, $rawfitdata);
		GL.RasterPos2i(0, $window_height.to_f*2.0/3.0+0.5);
		GL.DrawPixels($width, $height, GL::RGB, GL::FLOAT, $rawstgdata);
	end
	GL.PixelZoom(1.0,1.0);
	GL.LineWidth(1.0);
	if $grid then
		if $vsplit then
			GL.Begin(GL::LINES);
				GL.Color3f(0.0, 0.0, 0.0);
				($width*2).times do |ii|
					GL.Vertex3f($window_width.to_f*ii/$width/2.0,$window_height.to_f/3.0,0.0);
					GL.Vertex3f($window_width.to_f*ii/$width/2.0,$window_height.to_f,0.0);
				end
				($height).times do |ii|
					GL.Vertex3f(0,$window_height.to_f*(ii.to_f/$height.to_f*2.0+1.0)/3.0,0.0);
					GL.Vertex3f($window_width.to_f,$window_height.to_f*(ii.to_f/$height.to_f*2.0+1.0)/3.0,0.0);
				end
			GL.End();
		else
			GL.Begin(GL::LINES);
				GL.Color3f(0.0, 0.0, 0.0);
				$width.times do |ii|
					GL.Vertex3f($window_width.to_f*ii/$width,$window_height.to_f/3.0,0.0);
					GL.Vertex3f($window_width.to_f*ii/$width,$window_height.to_f,0.0);
				end
				($height*2).times do |ii|
					GL.Vertex3f(0,$window_height.to_f*(ii.to_f/$height.to_f+1.0)/3.0,0.0);
					GL.Vertex3f($window_width.to_f,$window_height.to_f*(ii.to_f/$height.to_f+1.0)/3.0,0.0);
				end
			GL.End();
		end
	end
	GL.LineWidth(3.0);
	GL.Begin(GL::LINES);
		GL.Color3f(0.0, 0.0, 0.0);
		GL.Vertex3f(0.0,$window_height.to_f/3.0,0);
		GL.Vertex3f($window_width.to_f,$window_height.to_f/3.0,0.0);
		if $vsplit then
			GL.Vertex3f($window_width.to_f/2.0,$window_height,0);
			GL.Vertex3f($window_width.to_f/2.0,$window_height.to_f/3.0,0.0);
		else
			GL.Vertex3f(0,2*$window_height.to_f/3.0,0);
			GL.Vertex3f($window_width.to_f,2*$window_height.to_f/3.0,0.0);
		end
		GL.Color3f(0.0, 1.0, 0.0);
		GL.Vertex3f($window_width*$time.to_f/$data.size,0.0,0.0);
		GL.Vertex3f($window_width*$time.to_f/$data.size,$window_height.to_f/3.0,0.0);
	GL.End();
	GLUT.SwapBuffers(); #GL.Flush();
end

$reshape = Proc.new do |w, h|
	GL.Viewport(0, 0, w,  h);
	GL.MatrixMode(GL::PROJECTION);
	GL.LoadIdentity();
	GL.Ortho(0, w, 0, h, -1.0, 1.0);
	GL.MatrixMode(GL::MODELVIEW);
	$window_width = w
	$window_height = h
	update
end

$timerfunc = Proc.new do |param|
	if $time >= $data.size - 1 then
		$animate = false
	elsif $animate then
		$time += 1
		update
		GLUT.PostRedisplay
	end
	exit 0 if GLUT.GetWindow == 0
	GLUT.TimerFunc(100,$timerfunc,0)
end

$keyboard = Proc.new do |key, x, y|
	case (key)
	when 27 then exit 0
	when ?q then exit 0
	when ?, then $time = [$time-1,0].max
	when ?< then $time = [$time-($data.size/20),0].max
	when ?. then $time = [$time+1,$data.size-1].min
	when ?> then $time = [$time+$data.size/20,$data.size-1].min
	when 32 then $animate = ! $animate
	when ?g then $grid = ! $grid
	when ?v then $vsplit = ! $vsplit
	when ?f then 
		$fullscreen = ! $fullscreen
		if $fullscreen then 
			GLUT.FullScreen 
		else 
			GLUT.PositionWindow(100,100) 
			GLUT.ReshapeWindow(500,500)
		end
	when ?m then $mean = ! $mean
	end
	update
	GLUT.PostRedisplay
end

def main
	begin	
		unless ARGV[0] and file = File.open(ARGV[0],"r") then
			print "Wrong file!\n"
			exit 1
		end
		parse(file)
	ensure
		file.close if file
	end
	update
	coopup

	GLUT.Init
	GLUT.InitDisplayMode(GLUT::DOUBLE | GLUT::RGB | GLUT::DEPTH);
	GLUT.InitWindowSize($window_width, $window_height);
	GLUT.InitWindowPosition(100, 100);
	GLUT.CreateWindow("KoVis : #{ARGV[0]}");
	GL.PixelStorei(GL::UNPACK_ALIGNMENT, 1);
	GL.ClearColor(0.0, 0.0, 0.0, 0.0);
	GLUT.ReshapeFunc($reshape);
	GLUT.KeyboardFunc($keyboard);
	GLUT.DisplayFunc($display);
	GLUT.TimerFunc(500,$timerfunc,0);
	GLUT.MainLoop();
end

main
